/*
 * Created by Jaren at 2022/7/16 11:09
 */
import 'dart:async';
import 'dart:core';
import 'dart:io';
import 'dart:typed_data';

import 'package:inmotion_protocol/base/file/file_manager.dart';
import 'package:inmotion_protocol/base/protocol.dart';
import 'package:inmotion_protocol/lorin/file/lorin_file_manager.dart';
import 'package:inmotion_protocol/lorin/file/response.dart';
import 'package:inmotion_protocol/lorin/lorin_message.dart';
import 'package:inmotion_protocol/protocol_worker.dart';
import 'package:inmotion_protocol/util/byte_util.dart';
import 'package:inmotion_protocol/util/log.dart';

class LorinFileDownloadManager extends LorinFileManager {
  LorinFileDownloadManager(Protocol<LorinMessage> protocol, LorinFileManagerConfig config) : super(protocol, config);

  @override
  Future<bool> checkFile(int target, int fileHandle, int offset, int length, Uint8ClampedList expectCheck) {
    return protocol.getFileCheck(target: target, fileHandle: fileHandle, offset: offset).then((fileCheckRsp) {
      fileCheckRsp as GetFileCheckResponse;

      if (!fileCheckRsp.isSuccess()) {
        throw FileOperationStepError(IFileManager.stepCheckFile, fileCheckRsp.originError);
      }
      context.retryTimes = 0;
      LogUtils.d("check file, remote file check: ${ByteUtil.bytes2HexString(fileCheckRsp.check)}, local file check: ${ByteUtil.bytes2HexString(expectCheck)}");
      if (!ByteUtil.arrayEquals(fileCheckRsp.check, expectCheck)) {
        throw FileOperationStepError.withExtra(IFileManager.stepCheckFile, fileCheckRsp.originError, 0);
      }
      return Future.value(true);
    }).catchError((error) {
      if (error is MsgTimeoutException) {
        if (context.retryTimes < maxRetryTimes()) {
          context.retryTimes++;
          return checkFile(target, fileHandle, offset, length, expectCheck);
        }
      }
      if (error is FileOperationStepError) {
        throw error;
      }
      throw FileOperationStepError(IFileManager.stepCheckFile, IFileManager.errorUnknown);
    });
  }

  @override
  Future<bool> prepareTransfer(int remoteFileLength, Uint8ClampedList remoteCheck, File localFile, StreamController progressTicker) {
    if (!localFile.existsSync() || localFile.lengthSync() == 0) {
      // 本地文件不存在或文件大小为0，从头开始传输
      return transferFile(context, localFile, 0, remoteFileLength, progressTicker);
    }
    // 本地文件已存在，判断校验是否一致，是否可以续传
    return checkCrc32(remoteCheck, localFile, remoteFileLength).then((checkResult) {
      if (checkResult) {
        // 校验一致，可以续传
        return transferFile(context, localFile, remoteFileLength, remoteFileLength, progressTicker);
      }
      // 校验不一致，从头开始传输
      // 先删除本地文件
      localFile.deleteSync();
      return transferFile(context, localFile, 0, remoteFileLength, progressTicker);
    });
  }

  @override
  Future<bool> transferFile(Context context, File file, int transferOffset, int remoteFileLength, StreamController progressTicker) {
    if (!file.existsSync()) file.createSync(recursive: true);
    return transferFileImpl(context, transferOffset, remoteFileLength, 0, file, progressTicker);
  }

  Future<bool> transferFileImpl(Context context, int initOffset, int remoteFileLength, int tab, File file, StreamController progressTicker) {
    int pkgSize = nextPkgSize(initOffset, context.fileLength, sizePeerPkg());
    if (pkgSize <= 0) {
      progressTicker.add(FileProgressEvent(initOffset, remoteFileLength, calAveSpeed(), calRtSpeed(), now() - context.startTransferTimestamp, calRemainSeconds()));
      return Future.value(true);
    }
    return transferFileImpl0(context, initOffset, tab, file, pkgSize).then((receivedLength) {
      if (checkInterrupted()) throw CanceledError();

      onTransferring(initOffset, pkgSize, context.fileLength);

      int nextOffset = initOffset + receivedLength;
      // trigger
      progressTicker.add(FileProgressEvent(initOffset + pkgSize, remoteFileLength, calAveSpeed(), calRtSpeed(), now() - context.startTransferTimestamp, calRemainSeconds()));

      if (nextPkgSize(nextOffset, remoteFileLength, sizePeerPkg()) <= 0) {
        return Future.value(true);
      }
      return transferFileImpl(context, nextOffset, remoteFileLength, tab + 1, file, progressTicker);
    });
  }

  Future<int> transferFileImpl0(Context context, int offset, int tab, File file, int nextPkgSize) {
    return protocol.getFile(fileHandle: context.fileHandle, offset: offset, tab: tab, target: context.target, contentLength: nextPkgSize).then((rsp) {
      rsp as ReadFileResponse;
      if (rsp.isSuccess()) {
        file.writeAsBytesSync(rsp.content, mode: FileMode.writeOnlyAppend, flush: true);
        return Future.value(rsp.content.length);
      }
      throw FileOperationStepError.withExtra(IFileManager.stepTransferFile, rsp.originError, offset);
    }).catchError((error) {
      if (error is FileOperationStepError) {
        throw error;
      }
      throw FileOperationStepError.withExtra(IFileManager.stepTransferFile, IFileManager.errorUnknown, offset);
    });
  }
}
